package ca.uvic.cs.cogz.mapping.ontology;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

import ca.uvic.cs.cogz.mapping.core.AbstractMapElement;
import ca.uvic.cs.cogz.util.FrameUtil;

import com.atlutil.ATLRuleWriter;
import com.atlutil.CannotMapException;
import com.atlutil.CardinalityException;
import com.atlutil.InvalidReferenceException;
import com.atlutil.UICallbackMapping;

import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Frame;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.prompt.operation.Operation;

public class TransformationEngine {
	private static TransformationEngine transformationEngine;
	
	private List<String> instantiatedVariableNames;
	private List<Cls> instantiatedClasses;
	
	private List<EPackage> sourceEcorePackage;
	private List<EPackage> targetEcorePackage;
	private ATLRuleWriter ruleWriter;
	
	private TransformationEngine() {
		String sourceEcoreFile = DataLoaderFacade.getInstance().getSourceOntology().getProject().getProjectURI().getPath().replace("pprj", "ecore");
		String targetEcoreFile = DataLoaderFacade.getInstance().getTargetOntology().getProject().getProjectURI().getPath().replace("pprj", "ecore");
		
		instantiatedVariableNames = new ArrayList<String>();
		instantiatedClasses = new ArrayList<Cls>();
		
		sourceEcorePackage = loadEcore(sourceEcoreFile);
		targetEcorePackage = loadEcore(targetEcoreFile);
	}
	
	public static TransformationEngine getInstance() {
		if(transformationEngine == null) {
			transformationEngine = new TransformationEngine();
		}
		return transformationEngine;
	}
	
	public List<String> getInstatiatedVariableNames() {
		return instantiatedVariableNames;
	}
	
	public List<Cls> getInstatiatedClasses() {
		return instantiatedClasses;
	}
	
	/**
	 * Assigns a value to a slot property for the target frame in a mapping.
	 */
	public void setPropertyValue(AbstractMapElement mapElement, Slot property, String value) {
		if(sourceEcorePackage == null || targetEcorePackage == null) return;
		
		String propertyName = FrameUtil.cleanFrameName(property.getName());
		
		String sourceFrameName = FrameUtil.cleanFrameName(mapElement.getSourceFrame().getName());
		String targetFrameName = FrameUtil.cleanFrameName(mapElement.getTargetFrame().getName());
		
		EPackage sourcePackage = getEPackage(sourceEcorePackage, sourceFrameName);
		EPackage targetPackage = getEPackage(targetEcorePackage, targetFrameName);
		
		EClass sourceClass = (EClass)sourcePackage.getEClassifier(sourceFrameName);
		EClass targetClass = (EClass)targetPackage.getEClassifier(targetFrameName);
		
		ruleWriter = getRuleWriter(sourcePackage, targetPackage);
		
		EStructuralFeature targetAttribute = getEStructuralFeature(targetClass, propertyName);
	
		try {
			if(targetAttribute instanceof EAttribute) {
				ruleWriter.setProperty(sourceClass, targetClass, (EAttribute)targetAttribute, value);
			}
			else {
				ruleWriter.setProperty(sourceClass, targetClass, (EReference)targetAttribute, value);
			}
		} catch (CannotMapException e) {
			e.printStackTrace();
		}
		
		try {
			System.out.println(ruleWriter.writeRules());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public String getRules() {
		try {
			return ruleWriter.writeRules();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	/**
	 * Creates a transformation rule based on the passed in Operation object.
	 */
	public void createTransformation(Operation operation) {
		if(sourceEcorePackage == null || targetEcorePackage == null) return;
		
		Frame sourceFrame = (Frame)operation.getArgs().getArg(0);
		Frame targetFrame = (Frame)operation.getArgs().getArg(1);
		
		if(sourceFrame instanceof Cls && targetFrame instanceof Cls) {
			createClsToClsTransformation((Cls)sourceFrame, (Cls)targetFrame);
		}
		else if(sourceFrame instanceof Slot && targetFrame instanceof Slot) {
			createSlotToSlotTransformation((Slot)sourceFrame, (Slot)targetFrame);
		}
	}
	
	/**
	 * Instantiates the given cls as a Class in the transformation.
	 */
	public void instantiateClass(Cls cls) {
		if(sourceEcorePackage == null || targetEcorePackage == null) return;
		
		String frameName = FrameUtil.cleanFrameName(cls.getName());
		
		EPackage targetPackage = getEPackage(targetEcorePackage, frameName);
		EClass targetClazz = (EClass)targetPackage.getEClassifier(frameName);
		
		ruleWriter = getRuleWriter(sourceEcorePackage.get(0), targetPackage);
		String instantiatedName = ruleWriter.instantiateConcept(targetClazz);
		
		instantiatedClasses.add(cls);
		instantiatedVariableNames.add(instantiatedName);
		
		try {
			System.out.println(ruleWriter.writeRules());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private ATLRuleWriter getRuleWriter(EPackage sourcePackage, EPackage targetPackage) {
		if(ruleWriter == null) {
			ruleWriter = new ATLRuleWriter("MyMapping", sourcePackage, targetPackage, new UICallbackMapping() {
				public boolean addClassToClassMapping(EClass source,
						EClass target) {
					return false;
				}
				
			});
		}
		
		return ruleWriter;
	}
	
	/**
	 * Creates a transformation rule to map the given sourceSlot to the targetSlot.  To get the associated Cls for the given slots
	 * we use the domain of the slot.  We use the associated Cls to get the EClass and then get the matching EStructuralFeature
	 * for the slots.  We then add an attribute mapping for data type slots or a reference mapping for reference slots.
	 * 
	 * For this to work, the domain Cls object's must have already been mapped.
	 */
	@SuppressWarnings("unchecked")
	private void createSlotToSlotTransformation(Slot sourceSlot, Slot targetSlot) {
		String sourceSlotName = FrameUtil.cleanFrameName(sourceSlot.getName());
		String targetSlotName = FrameUtil.cleanFrameName(targetSlot.getName());
		
		Collection domain = sourceSlot.getDirectDomain();
		Cls sourceCls = (Cls)((List)domain).get(0);
		
		Cls targetCls = null;
		if(targetSlot instanceof RDFProperty) {
			RDFProperty property = (RDFProperty)targetSlot;
			domain = property.getDomains(false);
			
			targetCls = (Cls)((List)domain).get(0);
		}
		else {
			domain = targetSlot.getDirectDomain();
			targetCls = (Cls)((List)domain).get(0);
		}
		
		String sourceFrameName = FrameUtil.cleanFrameName(sourceCls.getName());
		String targetFrameName = FrameUtil.cleanFrameName(targetCls.getName());
		
		EPackage sourcePackage = getEPackage(sourceEcorePackage, sourceFrameName);
		EPackage targetPackage = getEPackage(targetEcorePackage, targetFrameName);
		
		EClass sourceClass = (EClass)sourcePackage.getEClassifier(sourceFrameName);
		EClass targetClass = (EClass)targetPackage.getEClassifier(targetFrameName);
		
		EStructuralFeature sourceAttribute = getEStructuralFeature(sourceClass, sourceSlotName);
		EStructuralFeature targetAttribute = getEStructuralFeature(targetClass, targetSlotName);
	
		ruleWriter = getRuleWriter(sourcePackage, targetPackage);
		
		if(sourceAttribute instanceof EAttribute && targetAttribute instanceof EAttribute) {
			try {
				ruleWriter.addAttributeMapping((EAttribute)sourceAttribute, (EAttribute)targetAttribute);
			} catch (CannotMapException e1) {
				e1.printStackTrace();
			}
		}
		else if(sourceAttribute instanceof EReference && targetAttribute instanceof EReference) {
			try {
				ruleWriter.addReferenceMapping((EReference)sourceAttribute, (EReference)targetAttribute);
			} catch (CannotMapException e1) {
				e1.printStackTrace();
			} catch (CardinalityException e) {
				e.printStackTrace();
			} catch (InvalidReferenceException e) {
				e.printStackTrace();
			}
		}
		else { // FIXME
			try {
				throw new CannotMapException();
			} catch (CannotMapException e) {
				e.printStackTrace();
			}
		}
		
		try {
			System.out.println(ruleWriter.writeRules());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Creates a transformation rule to map the sourceCls to the targetCls.  We first get the EPackage associated with the 
	 * source and target, then get the EClass's for the source and target and use the ruleWriter to create a mapping rule between
	 * the two classes.
	 */
	private void createClsToClsTransformation(Cls sourceCls, Cls targetCls) {
		String sourceFrameName = FrameUtil.cleanFrameName(sourceCls.getName());
		String targetFrameName = FrameUtil.cleanFrameName(targetCls.getName());
		
		EPackage sourcePackage = getEPackage(sourceEcorePackage, sourceFrameName);
		EPackage targetPackage = getEPackage(targetEcorePackage, targetFrameName);
		
		EClass sourceClass = (EClass)sourcePackage.getEClassifier(sourceFrameName);
		EClass targetClass = (EClass)targetPackage.getEClassifier(targetFrameName);
		
		ruleWriter = getRuleWriter(sourcePackage, targetPackage);
		ruleWriter.addMappingRule(sourceClass, targetClass);
		
		try {
			System.out.println(ruleWriter.writeRules());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	private EStructuralFeature getEStructuralFeature(EClass clazz, String featureName) {
		for(EStructuralFeature structuralFeature : clazz.getEAllStructuralFeatures()) {
			if(structuralFeature.getName().equals(featureName)) return structuralFeature;
		}
		
		return null;
	}
	
	private EPackage getEPackage(List<EPackage> packages, String clazzName) {
		for(EPackage pack : packages) {
			if(pack.getEClassifier(clazzName) != null) {
				return pack;
			}
		}
		
		return null;
	}
	
	private List<EPackage> loadEcore(String ecoreFilePath) {
		ResourceSet resourceSet = new ResourceSetImpl();
		resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl());
		resourceSet.getPackageRegistry().put(EcorePackage.eNS_URI, EcorePackage.eINSTANCE);
		Resource resource = resourceSet.getResource(URI.createFileURI(ecoreFilePath), true);
		
		try {
			resource.load(null);
			
			List<EPackage> packages = new ArrayList<EPackage>();
			
			for(EObject eObject : resource.getContents()) {
				if(eObject instanceof EPackage) {
					EPackage pack = (EPackage)eObject;
					packages.add(pack);
					if(pack.getESubpackages() != null && pack.getESubpackages().size() > 0) {
						packages.addAll(pack.getESubpackages());
					}
				}
			}
			
			return packages;
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return null;
	}
}
